#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <stack>
#include <boost/format.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/logic/tribool.hpp>

#include "adventure-loop.hh"
#include "p-machine.hh"
#include "read_data.hh"
#include "simple_reader.hh"
#include "editor.hh"
#include "condition_check.hh"

using namespace std;
using boost::format;
using boost::tribool;
using boost::indeterminate;


/*! \brief do some action
 * 
 * Perform all actions and return true. If false is returned the
 * normal program progress is continued. This means that movement
 * verbs will be executed!
 *
 * In future this will be a simple stack based P-code machine. The
 * machine has a stack for ints (0 = false, 1 = true, -1 = unknown,
 * -32768 = error). And it has the registers:
 *
 * - l location
 * - v verb
 * - a adverb
 * - o1/o object 1
 * - o2/O object 2
 * - j advective 1
 * - J adjective 2
 *
 * Each opcode is 3 int16_t long...
 *
 * \param adv reference to the adventure object
 * \param actions reference to the actions to be done
 * \return normally true to tell you that everything has be done
 */
bool do_action(Adventure &adv, const vector<Cmd> &actions) {
  std::stack<int> stack; //In preparation for P-code
  bool ret = true; // This is the default
  for(unsigned int i = 0; i < actions.size(); ++i) {
    Cmd acmd = actions[i];
    switch(acmd.c) {
    case 0xDEAD: //dead
    case '^':
      adv.dead = true;
      break;
    case '=':
      adv.counters.at(acmd.p1) = acmd.p2;
      break;
    case '+':
      adv.counters.at(acmd.p1) += acmd.p2;
      break;
    case '-':
      adv.counters.at(acmd.p1) -= acmd.p2;
      break;
    case 'd': //drop
      if(adv.obj1 < 0)
	throw invalid_argument("Guru Meditation: adv.obj1<0 for drop");
      adv.objects[adv.obj1].pos = adv.here;
      break;
    case 'e': //examine
      if(adv.obj1 < 0)
	cout << "Examine what?\n";
      else
	cout << adv.objects[adv.obj1].desc << '\n';
      break;
    case 'g': //go to
      if(acmd.p1 < 0)
	throw invalid_argument("Guru Meditation: acmd.p1<0 for go to");
      adv.here = acmd.p1;
      break;
    case 'i': //inventory
      cout << adv.inventory();
      break;
    case 'l':
      if(adv.here < 0)
	throw invalid_argument("Guru Meditation: here<0 for look");
      cout << adv.locations[adv.here].desc_long << ' ';
      break;
    case 'p': //pick up
      if(adv.obj1 < 0)
	throw invalid_argument("Guru Meditation: adv.obj1<0 for take");
      if(adv.objects[adv.obj1].weight < 0)
	cout << "You can not carry that!\n";
      else
	adv.objects[adv.obj1].pos = POS_CARRIED;
      break;
    case 'P': //pick up object ?
      if(acmd.p1 < 0)
	throw invalid_argument("Guru Meditation: acmd.p1<0 for take");
      if(adv.objects[acmd.p1].weight < 0)
	cout << "You can not carry that!\n";
      else
	adv.objects[acmd.p1].pos = POS_CARRIED;
      break;
    case 0x71756974: //quit
    case 'q':
      adv.end = true;
      break;
    case 0x73636f72: //scor
      cout << "Your current score is " << adv.counters.at(32767) << " points.\n";
      break;
    case 'x': //Add an exit to the current room
      /* p1 = verb, p2 = room to exit to, if room is == -1 then remove
	 this connection. */
      if(acmd.p1 < 0) {
	std::ostringstream oss;
	oss << "Guru Meditation: acmd.p1<0 for add_exit (read from line " << acmd.line << ')';
	throw invalid_argument(oss.str());
      }
      if(acmd.p2 < -1) {
	std::ostringstream oss;
	oss << "Guru Meditation: acmd.p2<-1 for add_exit (read from line " << acmd.line << ')';
	throw invalid_argument(oss.str());
      }
      if(static_cast<unsigned int>(acmd.p2) >= adv.locations.size())
	throw invalid_argument("Guru Meditation: trying to connect to a non-existing room in acmd.p2 for add_exit");
      if(acmd.p2 >= 0) {
	adv.locations[adv.here].exits[acmd.p1] = acmd.p2;
      } else {
	adv.locations[adv.here].exits.erase(acmd.p1);
      }
      break;
    case 'y': //destroy current object
      if(adv.obj1 < 0)
	throw invalid_argument("Guru Meditation: obj1<0 for destroy object");
      adv.objects[adv.obj1].pos = POS_KILLED;
      break;
    case 'Y': //destroy object ?
      if(acmd.p1 < 0)
	throw invalid_argument("Guru Meditation: acmd.p1<0 for destroy object");
      adv.objects[acmd.p1].pos = POS_KILLED;
      break;
    case 'w': //write msg
      if(acmd.p1 < 0)
	throw invalid_argument("Guru Meditation: acmd.p1<0 for print message");
      if(static_cast<unsigned int>(acmd.p1) < adv.msgs.size())
	cout << adv.msgs[acmd.p1] << endl;
      else
	throw invalid_argument(str(format("message number too high ($%X > $%X)") % acmd.p1 % adv.msgs.size()));
      break;
    case 'M': //move object to, if POS_HERE then to here
      adv.objects[acmd.p1].pos = acmd.p2 == POS_HERE ? adv.here : acmd.p2;
      break;
    case '~':
      ret = false;
      break;
    default:
      throw runtime_error(str(format("unknown action %d $%X '%c'") % acmd.c % acmd.c % static_cast<char>(acmd.c)));
    }
  }
  return ret;
}

bool do_event(Adventure &adv, const vector<Event> &events) {
  for(unsigned long i = 0; i < adv.events.size(); ++i) {
    const Event *p = &(adv.events[i]);
    //cout << format("event %p (i=%d): location=%d,verb=%d,adverb=%d,obj1=%d,adj1=%d,obj2=%d,adj2=%d\n") % p % i % p->location % p->verb % p->adverb % p->obj1 % p->adj1 % p->obj2 % p->adj2;
    if(
       (p->location == adv.here || p->location < 0) &&
       ((p->verb == adv.verb) || (p->verb == EVENT_ANY && adv.verb >= 0)) &&
       ((p->adverb == adv.adverb) || (p->adverb == EVENT_ANY && adv.adverb >= 0)) &&
       ((p->obj1 == adv.obj1) || (p->obj1 == EVENT_ANY && adv.obj1 >= 0)) &&
       ((p->obj2 == adv.obj2) || (p->obj2 == EVENT_ANY && adv.obj2 >= 0)) &&
       ((p->adj1 == adv.adj1) || (p->adj1 == EVENT_MAYBE) || (p->adj1 == EVENT_ANY && adv.adj1 >= 0)) &&
       ((p->adj2 == adv.adj2) || (p->adj1 == EVENT_MAYBE) || (p->adj2 == EVENT_ANY && adv.obj1 >= 0))
       ) {
      //cout << p << " matches.\n";
      if(!p->pcode.empty()) {
	try {
	  int r = prun(adv, p->pcode);
	  if(r) {
	    //prun returned true!
	    return true;
	  }
	}
	catch(...) {
	  cerr << "Exception in pcode (" << p << "): " << hex;
	  std::copy(p->pcode.begin(), p->pcode.end(), std::ostream_iterator<int>(cerr, " "));
	  cerr << dec << endl;
	  throw;
	}
      }
      if(check_condition(adv, p->conditions)) {
	//cout << "All conditions met!\n";
	return do_action(adv, p->actions);
      }
    }
  }
  return false; //Nothing done
}

/*! \brief move the player
 *
 * Move the player according to the verb found in the adv structure.
 *
 * \param adv adventure object
 * \return true if movement successful or false if movement not successful, indeterminate: no movement verb
 */
tribool do_movement(Adventure& adv) {
  if(adv.movement_verbs.find(adv.verb) != adv.movement_verbs.end()) {
    ExitMap::const_iterator it = adv.locations[adv.here].exits.find(adv.verb);

    if(it != adv.locations[adv.here].exits.end()) {
      adv.here = it->second;
      return true;
    }
    return false;
  }
  return indeterminate;
}

/*
 * The main loop should do the following:
 * high priority conditions, this includes:
 *  - description of objects, exits, etc.
 *  - location is not yet visited!
 *  - returns a value which is 0 (normal continue) or has set one of the following bits, see skip
 * get input (0x1 if skip)
 * parse input, print parse error if necessary
 * (low priority) conditions: (0x2 if skip)
 *  - does print all the nice messages
 *  - handles events
 *  - handles special events (must return if movement is to be done!)
 * move player according to movement verbs given and the status (0x4 skip)
 * loop according to status
 *
 * status changes:
 *  - player can die
 *  - player can exit/quit the game
 *  - player wants to save/load the game
 *  - player needs to be moved
 */


Adventure &adventure_loop(Adventure &adv) {
  string line, tmp;
  tribool last_verb_has_moved = true;

  do {
    string verbstr;
    ++adv.turns;
    adv.verb = -1;
    adv.adverb = -1;
    adv.obj1 = -1;
    adv.adj1 = -1;
    adv.obj2 = -1;
    adv.adj2 = -1;

    /* High priority events. */
    for(auto it = adv.hp_events.begin(); it != adv.hp_events.end(); ++it) {
      register int ret = prun(adv, *it);
      if(ret == 1) break;
    }
    cout << endl << "? " << flush;
    getline(cin, line);
    if(line.empty()) continue;
    if(line.size() > 0 && line[0] == '!') {
      edit(adv, line.substr(1));
      continue;
    }
    boost::to_lower(line);
    istringstream in(line);
    in >> verbstr;
    adv.verb = adv.verbs.find(verbstr) != adv.verbs.end() ? adv.verbs.find(verbstr)->second : -1;
    while(in >> tmp) {
      int i;
      //Find *first* adjective. If we have an object1 already it must
      //be the second adjective, see later...
      if(adv.adj1 < 0 && adv.obj1 < 0) {
	auto it = adv.adjectives.find(tmp);
	if(it != adv.adjectives.end()) {
	  adv.adj1 = it->second;
	  continue;
	}
      }
      if(adv.adj2 < 0) { //Second adjective?
	auto it = adv.adjectives.find(tmp);
	if(it != adv.adjectives.end()) {
	  adv.adj2 = it->second;
	  continue;
	}
      }
      //Find the objects now...
      if(adv.obj1 < 0) { //We do not have the first object.
	if((i = adv.find_object(tmp, adv.adj1)) >= 0) {
	  adv.obj1 = i;
	  continue;
	}
      }
      if(adv.obj2 < 0) {
	if((i = adv.find_object(tmp, adv.adj2)) >= 0) {
	  adv.obj2 = i;
	  continue;
	}
      }
      //ignore too many objects
    }
    if(adv.verb < 0) {
      cout << "I do not know how to `" << verbstr << "`.\n";
    } else {
      //cout << format("verb=%d,adverb=%d,obj1=%d,adj1=%d,obj2=%d,adj2=%d\n") % verb % adverb % obj1 % adj1 % obj2 % adj2;
      bool event_done = do_event(adv, adv.events);
      adv.locations.at(adv.here).visited = true;
      if(!event_done) {
	last_verb_has_moved = do_movement(adv);
	if(indeterminate(last_verb_has_moved)) {
	  cout << "I do not understand!\n";
	} else if(!last_verb_has_moved) {
	  cout << "You can not go in that direction!\n";
	}
      }
    }
    {
      ofstream out("SIMPLE");
      write_to_file(adv, out);
    }
  } while(!adv.end && !adv.dead && adv.here >= 0);
  return adv;
}
